import zope.interface

from logging import getLogger

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.sql.expression import and_,or_,between
from sqlalchemy.exceptions import OperationalError

from meercat.interfaces import IPickleable
from meercat.harvester.base.interfaces import IQueriableHarvester
from meercat.harvester.voyager.declarative import createClasses



log = getLogger('meercat.harvester.voyager.harvester')

class VoyagerHarvester(object):
    zope.interface.implements(IQueriableHarvester)
    
    def __init__(self, name, *args, **kwargs):
        self.setSource(name)
        kwargs['strategy'] = 'threadlocal'
        
        if 'schema' in kwargs:
            kwargs['schema'] = str(kwargs['schema'])
        self._engine_args = (args, kwargs)
        self._setupConnection()
        
    
    def __getstate__(self):
        state = {}
        state['source'] = self.getSource()
        state['engine'] = self._engine_args
        return state
        
    
    def __setstate__(self, state):
        self.setSource(state['source'])
        self._engine_args = state['engine']
        self._setupConnection()
    
    def _setupConnection(self):
        args, kwargs = self._engine_args
        self._engine = create_engine(*args, **kwargs)
        
        self._Base = declarative_base(name='VoyagerHarvesterBase',
                                      bind=self._engine)
        self.Session = sessionmaker(bind=self._engine, autoflush=False, autocommit=False)
        
        for k,v in createClasses(self).iteritems():
            setattr(self,'__%s' % (k,), v)
    
    def getSource(self):
        """Return the name of the source that this harvester is harvesting from"""
        return self._source
        
      
    def setSource(self, name):
        """Set the name of the source that this harvester is harvesting from"""
        self._source = str(name)
    
    def getDeclarativeBase(self):
        return self._Base
    
    def getClass(self, _class):
        return getattr(self,'__%s' % (str(_class),))
    
    def harvest(self, limit=None, offset=None, from_date=None, until_date=None):
        log.debug('%s.harvest(limit=%s, offset=%s, from_date=%s, until_date=%s) started' % (str(self), str(limit), str(offset), str(from_date), str(until_date)))
        session = self.Session()
        
        try:
            q = session.query(self.getClass('VoyagerBibRecord')).order_by(self.getClass('VoyagerBibRecord').id)
            
            if from_date and until_date:
                q=q.join('holdings','items','item','status').filter(or_(
                    between(self.getClass('VoyagerBibRecord').created, from_date, until_date),
                    between(self.getClass('VoyagerBibRecord').modified, from_date, until_date),
                    between(self.getClass('VoyagerHolding').created, from_date, until_date),
                    between(self.getClass('VoyagerHolding').modified, from_date, until_date),
                    between(self.getClass('VoyagerHolding').export_ok_date, from_date, until_date),
                    between(self.getClass('VoyagerItem').created, from_date, until_date),
                    between(self.getClass('VoyagerItem').modified, from_date, until_date),
                    between(self.getClass('VoyagerItemStatus').date,
                            from_date, until_date),)).reset_joinpoint().enable_eagerloads(False).distinct()
            elif from_date:
                results = []
                log.debug('%s adding bib record filter' % (str(self),))
                results.extend(q.filter(or_(
                    self.getClass('VoyagerBibRecord').created >= from_date,
                    self.getClass('VoyagerBibRecord').modified >= from_date,)).enable_eagerloads(False).distinct().all())
                
                log.debug('%s adding holding filter' % (str(self),))
                results.extend(q.join('holdings').filter(or_(
                    self.getClass('VoyagerHolding').created >= from_date,
                    self.getClass('VoyagerHolding').modified >= from_date,
                    self.getClass('VoyagerHolding').export_ok_date >= from_date,)).reset_joinpoint().enable_eagerloads(False).distinct().all())
                
                log.debug('%s adding item filter' % (str(self),))
                results.extend(q.join('holdings','items','item').filter(or_(
                    self.getClass('VoyagerItem').created >= from_date,
                    self.getClass('VoyagerItem').modified >= from_date,)).reset_joinpoint().enable_eagerloads(False).distinct().all())
                
                log.debug('%s adding item status filter' % (str(self),))
                results.extend(q.join('holdings','items','item','status').filter(or_(
                    self.getClass('VoyagerItemStatus').date >= from_date,)).reset_joinpoint().enable_eagerloads(False).distinct().all())
                
                log.debug('%s pickling instances' % (str(self),))
                return map(IPickleable, results)
                
                """
                q=q.join('holdings','items','item','status').filter(or_(
                    self.getClass('VoyagerBibRecord').created >= from_date,
                    self.getClass('VoyagerBibRecord').modified >= from_date,
                    self.getClass('VoyagerHolding').created >= from_date,
                    self.getClass('VoyagerHolding').modified >= from_date,
                    self.getClass('VoyagerHolding').export_ok_date >= from_date,
                    self.getClass('VoyagerItem').created >= from_date,
                    self.getClass('VoyagerItem').modified >= from_date,
                    self.getClass('VoyagerItemStatus').date >= from_date,)).reset_joinpoint().enable_eagerloads(False).distinct()
                """
            elif until_date:
                q=q.join('holdings','items','item','status').filter(or_(
                    self.getClass('VoyagerBibRecord').created <= until_date,
                    self.getClass('VoyagerBibRecord').modified <= until_date,
                    self.getClass('VoyagerHolding').created <= until_date,
                    self.getClass('VoyagerHolding').modified <= until_date,
                    self.getClass('VoyagerHolding').export_ok_date <= until_date,
                    self.getClass('VoyagerItem').created <= until_date,
                    self.getClass('VoyagerItem').modified <= until_date,
                    self.getClass('VoyagerItemStatus').date <= until_date,)).reset_joinpoint().enable_eagerloads(False).distinct()
            
            if offset:
                q = q.offset(offset)
            
            if limit:
                q = q.limit(limit)
            
            return map(IPickleable, q.all())
        except Exception, e:
            log.error('Error while harvesting: %s' % (str(e),))
            import sys, traceback
            for line in traceback.format_exception(*sys.exc_info()):
                log.error(line)
            raise e
        finally:
            session.close()
            log.debug('%s.harvest(limit=%s, offset=%s, from_date=%s, until_date=%s) done' % (str(self), str(limit), str(offset), str(from_date), str(until_date)))
            
            
    
    def get(self, identifier):
        log.debug('%s.get(identifier=%s)' % (str(self), str(identifier),))
        session = self.Session()
        
        try:
            return IPickleable(session.query(self.getClass('VoyagerBibRecord')).get(identifier))
        finally:
            session.close()
    
    def count(self):
        log.debug('%s.count()' % (str(self)))
        session = self.Session()
        try:
            return session.query(self.getClass('VoyagerBibRecord')).count()
        finally:
            session.close()
